﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using Passwords.Models;
using System.Windows.Input;
using System.Windows;
using System.Collections.ObjectModel;
using System.IO.IsolatedStorage;
using Passwords.Utils;
using System.Windows.Controls;
using System.Diagnostics;
using Passwords.Services;
using Passwords.DAL;
using System.Security;
using System.Threading;
using Passwords.Events;
using System.Windows.Threading;


namespace Passwords.ViewModels
{
    public class DataViewModel : ViewModelBase, IDisposable, IDataContext
    {
        const string emptyPasswordTitle = "";
        #region ctor

        public DataViewModel(DataContext context)
            : base()
        {
            if (context == null) throw new ArgumentNullException("context");
            this.context = context;

            BindCommands();
        }

        public void Dispose()
        {

            if (App.Current != null)
            {
                Window w = Window;
                if (w != null)
                {
                    UnbindCommands();
                }
            }
            GC.SuppressFinalize(this);
        }

        ~DataViewModel()
        {
            Dispose();
        }

        #endregion
        #region fields

        private ObservableCollection<PasswordContract> searchResults;
        private bool isPasswordGenVisible;
        private PasswordGenViewModel passwordGen;
        private string searchText;
        private DispatcherTimer syncTimer;
        private SyncDisplayMode syncDisplayMode;
        private SyncContext syncContext;
        private List<CommandBinding> bindings = new List<CommandBinding>();
        private IList<PasswordContract> _selectedPasswords;
        private bool isCategoryFocused;
        private bool isTemplateUnlocked = true;
        private double minWidth = 320d;
        private string originalCategoryName;
        private string originalPasswordName;
        private bool isCategoryModified;
        private int hiddenCategoryIndex;
        private bool isCategoryListVisible = true;
        private DisplayMode displayMode = DisplayMode.Passwords;
        private AppDisplayMode appDisplayMode = AppDisplayMode.Login;
        private ObservableCollection<PasswordContract> passwords;
        private ObservableCollection<PasswordContract> favorites;
        //        private ObservableCollection<AlphaItem> alphaList;
        private ObservableCollection<CategoryContract> categories;
        private ObservableCollection<CategoryContract> templates;
        private DataContext context;
        private PasswordContract selectedPassword;
        private CategoryContract currentCategory;
        private CategoryContract selectedCategory;
        private CategoryContract editCategory;

        private ObservableCollection<PasswordContract> selectedPasswords;
        private ObservableCollection<SelectedCategory> selectedCategories;
        private ObservableChildCollection<FieldContract> editFields;
        private ObservableChildCollection<FieldContract> categoryEditFields;
        private bool isNewPasswordEnabled;
        private FieldContract selectedEditField;

        #endregion
        #region props
        private bool isSearchEmpty = true;

        public bool IsSearchEmpty
        {
            get { return isSearchEmpty; }
            private set
            {
                if (isSearchEmpty != value)
                {
                    isSearchEmpty = value;
                    OnPropertyChanged("IsSearchEmpty");
                }
            }
        }


        /// <summary>
        /// Gets or sets the text in the search box.
        /// </summary>
        public string SearchText
        {
            get { return searchText ?? string.Empty; }
            set
            {
                if (searchText != value)
                {
                    searchText = value;
                    OnPropertyChanged("SearchText");
                    OnSearchTextChanged();
                    IsSearchEmpty = string.IsNullOrEmpty(value);
                }
            }
        }


        public PasswordGenViewModel PasswordGen
        {
            get { return passwordGen; }
            private set
            {
                if (passwordGen != value)
                {
                    passwordGen = value;
                    OnPropertyChanged("PasswordGen");
                }
            }
        }

        public bool IsPasswordGenVisible
        {
            get { return isPasswordGenVisible; }
            set
            {
                if (isPasswordGenVisible != value)
                {
                    isPasswordGenVisible = value;
                    OnPropertyChanged("IsPasswordGenVisible");
                    OnPasswordGenVisibleChanged();
                }
            }
        }

        public SyncContext SyncContext
        {
            get { return syncContext; }
            set
            {
                if (syncContext != value)
                {
                    syncContext = value;
                    OnPropertyChanged("SyncContext");
                    if (value == null)
                    {
                        SyncDisplayMode = Models.SyncDisplayMode.None;
                    }
                    else
                    {
                        SyncDisplayMode = SyncDisplayMode.Synchronize;
                    }
                }
            }
        }

        public SyncDisplayMode SyncDisplayMode
        {
            get { return syncDisplayMode; }
            set
            {
                if (syncDisplayMode != value)
                {
                    syncDisplayMode = value;
                    OnPropertyChanged("SyncDisplayMode");
                    if (value == Models.SyncDisplayMode.Synchronize) RestartSyncElapsedTimer();
                }
            }
        }

        protected byte[] Checksum;

        protected Window Window { get { return App.Current.MainWindow; } }

        public FieldContract SelectedEditField
        {
            get { return selectedEditField; }
            set
            {
                if (selectedEditField != value)
                {
                    selectedEditField = value;
                    OnPropertyChanged("SelectedEditField");
                }
            }
        }

        public bool IsCategoryFocused
        {
            get { return isCategoryFocused; }
            set
            {
                if (isCategoryFocused != value)
                {
                    isCategoryFocused = value;
                    OnPropertyChanged("IsCategoryFocused");
                }
            }
        }

        /// <summary>
        /// Gets whether the template of the current password is unlocked.
        /// </summary>
        public bool IsTemplateUnlocked
        {
            get { return isTemplateUnlocked; }
            private set
            {
                if (isTemplateUnlocked != value)
                {
                    isTemplateUnlocked = value;
                    OnPropertyChanged("IsTemplateUnlocked");
                }
            }
        }

        /// <summary>
        /// Gets  the minimum witdh for the window, which depends on the AppDisplayMode.
        /// </summary>
        public double MinWidth
        {
            get { return minWidth; }
            private set
            {
                if (minWidth != value)
                {
                    minWidth = value;
                    OnPropertyChanged("MinWidth");
                }
            }
        }

        /// <summary>
        /// Gets or sets the template for the current password.
        /// </summary>
        public CategoryContract Template
        {
            get { return selectedPassword != null ? selectedPassword.Template : null; }
            set
            {
                var oldValue = Template;
                if (oldValue != value)
                {
                    if (selectedPassword != null) selectedPassword.Template = value;
                    OnTemplateChanged(oldValue);
                    OnPropertyChanged("Template");
                }
            }
        }

        public AppSettings Settings { get { return App.Settings; } }

        /// <summary>
        /// Gets whether the selected category is modified.
        /// </summary>
        public bool IsCategoryModified
        {
            get { return isCategoryModified; }
            private set
            {
                if (isCategoryModified != value)
                {
                    isCategoryModified = value;
                    if (selectedCategory != null) selectedCategory.IsModified = value;
                    OnPropertyChanged("IsCategoryModified");
                }
            }
        }

        public DataContext Context { get { return context; } }

        public bool IsNewPasswordEnabled
        {
            get { return isNewPasswordEnabled; }
            set
            {
                if (isNewPasswordEnabled != value)
                {
                    isNewPasswordEnabled = value;
                    OnPropertyChanged("IsNewPasswordEnabled");
                }
            }
        }

        /// <summary>
        /// Gets the collection of edit fields used in edit category view.
        /// </summary>
        public ObservableChildCollection<FieldContract> CategoryEditFields
        {
            get { return categoryEditFields; }
            private set
            {
                if (categoryEditFields != value)
                {
                    categoryEditFields = value;
                    OnPropertyChanged("CategoryEditFields");
                }
            }
        }


        /// <summary>
        /// Gets the collection of edit fields used in edit password view.
        /// </summary>
        public ObservableChildCollection<FieldContract> EditFields
        {
            get { return editFields; }
            private set
            {
                if (editFields != value)
                {
                    editFields = value;
                    OnPropertyChanged("EditFields");
                }
            }
        }


        /// <summary>
        /// Gets or sets the category which is currently edited.
        /// </summary>
        public CategoryContract EditCategory
        {
            get { return editCategory; }
            set
            {
                if (editCategory != value)
                {
                    CategoryContract oldValue = editCategory;
                    editCategory = value;
                    OnPropertyChanged("EditCategory");
                    OnEditCategoryChanged(oldValue);
                }
            }
        }


        /// <summary>
        /// Gets the collection of SelectedCategories used in edit password view.
        /// </summary>
        public ObservableCollection<SelectedCategory> SelectedCategories
        {
            get { return selectedCategories; }
            private set
            {
                if (selectedCategories != value)
                {
                    selectedCategories = value;
                    OnPropertyChanged("SelectedCategories");
                }
            }
        }

        public bool IsPasswordSelected
        {
            get { return selectedPassword != null; }
        }


        public IList<PasswordContract> SelectedPasswords
        {
            get
            {
                // if (selectedPassword == null) OnDisplayModeChanged(displayMode);
                if (selectedPasswords == null) selectedPasswords = new ObservableCollection<PasswordContract>();
                return selectedPasswords;
            }
            private set
            {
                if (_selectedPasswords != value)
                {
                    _selectedPasswords = value;
                    var sel = SelectedPasswords;
                    if (sel != null)
                    {
                        ListUtil.Fill(selectedPasswords, value);
                    }
                    //   selectedPasswords = value;
                    //OnPropertyChanged("SelectedPasswords");
                }
            }
        }

        public CategoryContract CurrentCategory
        {
            get { return currentCategory; }
            set
            {
                if (currentCategory != value)
                {
                    currentCategory = value;
                    OnPropertyChanged("CurrentCategory");
                }
            }
        }

        public CategoryContract SelectedCategory
        {
            get { return selectedCategory; }
            set
            {
                originalCategoryName = value != null ? value.Name : string.Empty;
                if (selectedCategory != value)
                {
                    var oldValue = selectedCategory;
                    selectedCategory = value;
                    OnSelectedCategoryChanged(oldValue);

                    OnPropertyChanged("SelectedCategory");
                    if (AutoChangeCurrentCategory()) CurrentCategory = value;
                }
            }
        }


        public PasswordContract SelectedPassword
        {
            get { return selectedPassword; }
            set
            {
                originalPasswordName = value != null ? value.Name : string.Empty;
                if (selectedPassword != value)
                {
                    var oldValue = selectedPassword;
                    bool selected = IsPasswordSelected;
                    selectedPassword = value;
                    OnPropertyChanged("SelectedPassword");
                    OnSelectedPasswordChanged(oldValue);
                    if (selected != IsPasswordSelected)
                    {
                        OnPropertyChanged("IsPasswordSelected");
                    }
                }
            }
        }

        public ObservableCollection<PasswordContract> Passwords
        {
            get
            {
                if (passwords == null) LoadPasswords();
                return passwords;
            }
        }

        public IList<PasswordContract> Favorites
        {
            get
            {
                if (favorites == null) LoadPasswords();
                return favorites;
            }
        }

        /// <summary>
        /// Gets all templates which are those categories which have no fields.
        /// </summary>
        public ObservableCollection<CategoryContract> Templates
        {
            get
            {
                if (null == templates)
                {
                    templates = new ObservableCollection<CategoryContract>(Categories.Where(c => c.IsTemplate));
                }
                return templates;
            }
        }

        public ObservableCollection<CategoryContract> Categories
        {
            get
            {
                if (categories == null)
                {
                    categories = new ObservableCollection<CategoryContract>();
                    var ids = context.ReadCategoryIds();
                    if (ids != null)
                    {
                        foreach (string id in ids)
                        {
                            var category = ReadCategory(id);
                            if (category != null)
                            {
                                categories.SortInto(category);
                            }
                        }
                    }
                }
                return categories;
            }
        }

        public CategoryContract ReadCategory(string categoryId)
        {
            CategoryContract category = context.ReadCategory(categoryId);
            if (category != null)
            {
                category.ViewModel = this;
                var fields = category.TransferFields;
                if (fields != null)
                {
                    foreach (var field in fields) field.ViewModel = this;
                }
            }
            return category;
        }

        ///// <summary>
        ///// Gets a collection of passwords and group headers.
        ///// </summary>
        //public IList<AlphaItem> AlphaList
        //{
        //    get
        //    {
        //        if (alphaList == null) LoadPasswords();
        //        return alphaList;
        //    }
        //}

        public AppDisplayMode AppDisplayMode
        {
            get { return appDisplayMode; }
            set
            {
                if (appDisplayMode != value)
                {
                    var oldValue = appDisplayMode;
                    appDisplayMode = value;
                    OnAppDisplayModeChanged(oldValue);
                    OnPropertyChanged("AppDisplayMode");
                }
            }
        }

        public DisplayMode DisplayMode
        {
            get { return displayMode; }
            set
            {
                if (displayMode != value)
                {
                    var oldMode = displayMode;
                    displayMode = value;
                    OnPropertyChanged("DisplayMode");
                    OnDisplayModeChanged(oldMode);
                }
            }
        }

        public bool IsCategoryListVisible
        {
            get { return isCategoryListVisible; }
            set
            {
                if (isCategoryListVisible != value)
                {
                    isCategoryListVisible = value;
                    OnPropertyChanged("IsCategoryListVisible");
                }
            }
        }


        #endregion
        #region commands

        private void BindCommands()
        {
            BindCommand(Commands.CopyValue, CopyValue);


            BindCommand(Commands.OpenPasswordGen, OpenPasswordGenCommand);
            BindCommand(Commands.ClosePasswordGen, ClosePasswordGenCommand);

            BindCommand(Commands.CancelSearch, CancelSearch);
            BindCommand(Commands.BeginSearch, BeginSearch);
            BindCommand(BackupCommands.Backup, BackupCommand);

            BindCommand(new CommandBinding(Commands.DeletePasswordCommand, DeletePasswordCommand, CanModifyPassword));
            BindCommand(new CommandBinding(Commands.ToggleFaveCommand, ToggleFaveCommand));
            BindCommand(new CommandBinding(Commands.EditPasswordCommand, EditPasswordCommand, CanModifyPassword));
            BindCommand(new CommandBinding(Commands.NewPasswordCommand, NewPasswordCommand));
            BindCommand(new CommandBinding(Commands.SavePasswordCommand, SavePasswordCommand, CanSavePassword));
            BindCommand(new CommandBinding(Commands.UndoPasswordCommand, UndoPasswordCommand, CanUndoPassword));
            BindCommand(new CommandBinding(Commands.CancelPasswordCommand, CancelPasswordCommand));
            BindCommand(new CommandBinding(Commands.FieldUpCommand, FieldUpCommand, IsFieldUpEnabled));
            BindCommand(new CommandBinding(Commands.FieldDownCommand, FieldDownCommand, IsFieldDownEnabled));

            BindCommand(new CommandBinding(Commands.SaveCategoryCommand, SaveCategoryCommand, CanSaveCategory));
            BindCommand(new CommandBinding(Commands.UndoCategoryCommand, UndoCategoryCommand, CanUndoCategory));
            BindCommand(new CommandBinding(Commands.UndoEditCategoryCommand, UndoCategoryCommand));
            BindCommand(new CommandBinding(Commands.DeleteCategoryCommand, DeleteCategoryCommand, CanDeleteCategory));
            BindCommand(new CommandBinding(Commands.NewCategoryCommand, NewCategoryCommand, CanAddCategory));

            BindCommand(new CommandBinding(Commands.NewFieldCommand, NewFieldCommand, CanAddTemplateField));
            BindCommand(new CommandBinding(Commands.DeleteFieldCommand, DeleteFieldCommand));
            BindCommand(new CommandBinding(Commands.ViewPasswordCommand, ViewPasswordCommand, CanModifyPassword));
            BindCommand(new CommandBinding(Commands.PasswordsCommand, PasswordsCommand));
            BindCommand(new CommandBinding(Commands.CategoriesCommand, CategoriesCommand));
            BindCommand(new CommandBinding(Commands.RemoveFieldCommand, RemoveFieldCommand));
            BindCommand(new CommandBinding(Commands.AddFieldCommand, AddFieldCommand));
            BindCommand(new CommandBinding(Commands.DeleteCategoryItemCommand, DeleteCategoryItemCommand));

            BindCommand(new CommandBinding(Commands.SyncLoginCommand, SyncLoginCommand));
            BindCommand(new CommandBinding(Commands.ClearSyncPasswordErrorCommand, ClearSyncPasswordErrorCommand));
        }

        private void CopyValue(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            FieldContract field = e.Parameter as FieldContract;
            if (field != null) Clipboard.SetText(field.Value);
        }


        private void BeginSearch(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            if (DisplayMode == Models.DisplayMode.Search) DisplayMode = Models.DisplayMode.Passwords;
            DisplayMode = Models.DisplayMode.Search;
        }


        private void CancelSearch(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            DisplayMode = Models.DisplayMode.Passwords;
        }


        private void OpenPasswordGenCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            if (PasswordGen != null)
            {
                PasswordGen.Dispose();
                PasswordGen = null;
            }
            FieldContract field = (e.OriginalSource as FrameworkElement).DataContext as FieldContract;
            if (field != null)
            {
                PasswordGen = new PasswordGenViewModel
                {
                    Password = field.Value,
                    Field = field
                };
                IsPasswordGenVisible = true;
            }
        }

        private void ClosePasswordGenCommand(object sender, ExecutedRoutedEventArgs e)
        {
            var pwg = PasswordGen;
            if (string.Equals("true", e.Parameter) && pwg != null)
            {
                pwg.Field.Value = pwg.Password;
            }
            e.Handled = true;
            IsPasswordGenVisible = false;
            if (pwg != null) pwg.Dispose();
            PasswordGen = null;
        }

        void IsFieldUpEnabled(object sender, CanExecuteRoutedEventArgs e)
        {
            e.Handled = true;

            FieldContract field = selectedEditField;
            var list = CategoryEditFields;
            e.CanExecute = field != null && list != null && list.IndexOf(field) > 0;
            //FieldContract field = GetFieldContract(e);
            //int index = categoryEditFields != null ? categoryEditFields.IndexOf(field) : -1;
            //e.CanExecute = index > 0;
        }

        void IsFieldDownEnabled(object sender, CanExecuteRoutedEventArgs e)
        {
            e.Handled = true;

            FieldContract field = selectedEditField;
            var list = CategoryEditFields;
            e.CanExecute = field != null && list != null && list.IndexOf(field) < list.Count - 1;
            //FieldContract field = GetFieldContract(e);
            //int index = categoryEditFields != null ? categoryEditFields.IndexOf(field) : -1;
            //e.CanExecute = index > 0 || categoryEditFields.Count >= index + 1;
        }

        private void BackupCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            if (MessageBox.Show(AppResources.MsgBackupPasswords, AppResources.BackupBtn, MessageBoxButton.OKCancel) == MessageBoxResult.OK)
            {
                SyncContext syncContext = SyncContext.Current;
                if (syncContext != null)
                {
                    Backup backup = syncContext.CreateNewBackup(Guid.Empty, syncContext.DeviceId);

                    backup.Name = Environment.MachineName;
                    backup.DeviceId = syncContext.DeviceId;
                    backup.DateCreated = DateTime.Now;
                    backup.State = BackupState.Saved;
                    backup.PasswordHash = this.Context.PasswordHash;
                    backup.Categories = new ObservableCollection<CategoryContract>(Categories.Select(c => CreateBackupCategory(c)));
                    backup.Passwords = new ObservableCollection<PasswordContract>(Passwords.Select(p => CreateBackupPassword(p)));

                    syncContext.SaveBackup(backup, BackupState.Saved);
                }
            }

        }

        private PasswordContract CreateBackupPassword(PasswordContract password)
        {
            ServiceUtil.EncryptFields(password, Context.Aes);
            return password;
        }

        private CategoryContract CreateBackupCategory(CategoryContract c)
        {
            c.TransferFields = c.Fields.ToArray();
            return c;
        }

        private void FieldUpCommand(object sender, ExecutedRoutedEventArgs e)
        {
            //            FieldContract field = GetFieldContract(e);
            FieldContract field = selectedEditField;

            if (field != null && categoryEditFields != null)
            {
                int index = categoryEditFields.IndexOf(field) - 1;
                if (index >= 0)
                {
                    //   IsCategoryModified = true;
                    categoryEditFields.Move(index + 1, index);
                }
            }
        }

        private void FieldDownCommand(object sender, ExecutedRoutedEventArgs e)
        {
            //            FieldContract field = GetFieldContract(e);
            FieldContract field = selectedEditField;

            if (field != null && categoryEditFields != null)
            {
                int index = categoryEditFields.IndexOf(field);
                if (index >= 0 && index < (categoryEditFields.Count - 1))
                {
                    categoryEditFields.Move(index, index + 1);
                    //        IsCategoryModified = true;
                }
            }
        }

        private void DeleteFieldCommand(object sender, ExecutedRoutedEventArgs e)
        {
            FieldContract field = GetFieldContract(e);
            if (field != null && categoryEditFields != null && editCategory != null)
            {
                //                IsCategoryModified = true;
                categoryEditFields.Remove(field);
            }
        }

        private void NewFieldCommand(object sender, ExecutedRoutedEventArgs e)
        {
            if (categoryEditFields != null && editCategory != null)
            {
                //    IsCategoryModified = true;
                string name = "Field " + (categoryEditFields.Count + 1).ToString();
                FieldContract field = new FieldContract
                {
                    Id = Guid.NewGuid().ToString(),
                    InputScope = PhoneInputScopeNameValue.Default,
                    Name = name,
                    ViewModel = this
                };
                categoryEditFields.Add(field);
                SelectedEditField = field;
            }
        }

        private static FieldContract GetFieldContract(RoutedEventArgs e)
        {
            e.Handled = true;
            FrameworkElement fe = e.OriginalSource as FrameworkElement;
            FieldContract field = (fe != null ? fe.DataContext : null) as FieldContract;
            return field;
        }

        private void CancelPasswordCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            PasswordContract password = selectedPassword;
            if (password != null)
            {
                PasswordContract pw = context.ReadPassword(password.Guid.ToString());
                if (pw != null) password.CopyFrom(pw);
                if (password.IsNew)
                {
                    DeletePassword(password);
                    password = null;
                }
                AppDisplayMode = Models.AppDisplayMode.Passwords;
                SelectedPassword = password;
            }
        }


        private void UndoPasswordCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            PasswordContract password = selectedPassword;
            if (password != null && !password.IsNew)
            {
                PasswordContract pw = context.ReadPassword(password.Guid.ToString());
                if (pw != null) password.CopyFrom(pw);
                password.LoadFields();
                SelectedPassword = password;
            }
            else
            {
                PasswordContract pw = new PasswordContract();
                password.CopyFrom(pw);
                password.Fields.Clear();
                SelectedPassword = password;
            }
            EditFields = selectedPassword != null ? selectedPassword.GetEditFields(this) : null;

        }

        private void SaveCategoryCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            SaveCategory(editCategory, originalCategoryName);
            previousCategory = null;
        }

        public void SaveCategory(CategoryContract category, string originalName)
        {
            IsCategoryFocused = false;
            if (category != null && categoryEditFields != null)
                category.TransferFields = categoryEditFields.ToArray();

            category.ViewModel = this;
            DataUtil.SaveCategory(category, this, originalName);
            IsCategoryModified = false;
        }

        private void DeleteCategoryCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            CategoryContract catgory = editCategory;
            if (catgory != null)
            {
                if (MessageBox.Show(AppResources.MsgDeleteCategory,AppResources.DeleteBtn, MessageBoxButton.OKCancel) == MessageBoxResult.OK)
                {
                    DeleteCategory(catgory);
                }
            }
        }

        private CategoryContract previousCategory;

        private void NewCategoryCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            previousCategory = editCategory;
            AddCategory();
            IsCategoryModified = true;
            IsCategoryFocused = true;
        }


        private void UndoCategoryCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            IsCategoryFocused = false;
            if (IsCategoryModified)
            {
                CategoryContract category = editCategory;
                if (category != null)
                {
                    bool isNew = category.IsNew;
                    category.UndoCategory(this);
                    CreateIndexedFields();
                    IsCategoryModified = false;
                    if (isNew) EditCategory = previousCategory;
                    previousCategory = null;
                }
            }
            else
            {
                AppDisplayMode = Models.AppDisplayMode.Passwords;
            }

        }


        private void SavePasswordCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;

            PasswordContract password = selectedPassword;
            if (password != null)
            {
                bool isNew = password.IsNew;
                password.ViewModel = this;

                CategoryContract template = password.Template;

                password.UpdateFields(editFields);
                SavePassword(password, originalPasswordName);
                if (template != null && isNew) SelectedCategory = template;
                AppDisplayMode = Models.AppDisplayMode.Passwords;
                SelectedPassword = password;
                IsCategoryModified = false;
            }
        }

        public void SavePassword(PasswordContract password, string originalName)
        {
            CategoryContract template = password.Template;
            if (selectedCategories != null)
            {
                if (template != null)
                {
                    SelectedCategory selected = selectedCategories.FirstOrDefault(c => c.Category == template);
                    if (selected != null) selected.IsSelected = true;
                }
                DataUtil.UpdateCategories(password, this, selectedCategories.Where(c => c.IsSelected).Select(c => c.Category.Id).ToArray());
            }
            else
            {
                DataUtil.UpdateCategories(password, this, password.CategoryIds);
            }
            password.CategoryIds = Categories.Select(c => c.Id).ToArray();
            context.SavePassword(password);
            ReorderPassword(password, originalName);

        }

        private void SavePasswordInternal(PasswordContract password, string originalName)
        {
            CategoryContract template = password.Template;
            if (template != null)
            {
                SelectedCategory selected = selectedCategories.FirstOrDefault(c => c.Category == template);
                if (selected != null) selected.IsSelected = true;
            }
            if (selectedCategories != null) DataUtil.UpdateCategories(password, this, selectedCategories.Where(c => c.IsSelected).Select(c => c.Category.Id).ToArray());
            password.CategoryIds = Categories.Select(c => c.Id).ToArray();
            context.SavePassword(password);
            ReorderPassword(password, originalName);
        }


        /// <summary>
        /// Changes the index of the specified password in all possible collecitons.
        /// </summary>
        /// <param name="password">password to reorder</param>
        /// <param name="originalName">name of the password before it was changed.</param>
        private void ReorderPassword(PasswordContract password, string originalName)
        {
            if (_selectedPasswords != null && _selectedPasswords.Contains(password) && !selectedPasswords.Contains(password))
            {
                selectedPasswords.SortInto(password);
            }
            if (password.Name != originalName)
            {
                Passwords.Reorder(password, originalName);
                foreach (CategoryContract c in password.Categories)
                {
                    c.Passwords.Reorder(password, originalName);
                }

                if (selectedPasswords.Contains(password))
                {
                    selectedPasswords.Reorder(password, originalName);
                }
            }
        }

        void CanAddTemplateField(object sender, CanExecuteRoutedEventArgs e)
        {
            e.Handled = true;
            e.CanExecute = editCategory != null;
        }


        void CanSaveCategory(object sender, CanExecuteRoutedEventArgs e)
        {
            e.Handled = true;
            e.CanExecute = isCategoryModified
                && editCategory != null
                && !string.IsNullOrEmpty(editCategory.Name);
        }

        void CanUndoCategory(object sender, CanExecuteRoutedEventArgs e)
        {
            e.Handled = true;
            e.CanExecute = isCategoryModified;
        }

        void CanAddCategory(object sender, CanExecuteRoutedEventArgs e)
        {
            e.Handled = true;
            e.CanExecute = IsCategoryModified == false;
        }


        void CanDeleteCategory(object sender, CanExecuteRoutedEventArgs e)
        {
            e.Handled = true;
            e.CanExecute = editCategory != null && editCategory.Passwords.Count == 0 && !string.IsNullOrEmpty(editCategory.Id);
        }

        void CanModifyPassword(object sender, CanExecuteRoutedEventArgs e)
        {
            e.Handled = true;
            e.CanExecute = selectedPassword != null;
        }

        void CanUndoPassword(object sender, CanExecuteRoutedEventArgs e)
        {
            e.Handled = true;
            e.CanExecute = selectedPassword != null && !selectedPassword.IsNew;
        }


        void CanSavePassword(object sender, CanExecuteRoutedEventArgs e)
        {
            e.Handled = true;
            e.CanExecute = selectedPassword != null && !string.IsNullOrEmpty(selectedPassword.Name) && selectedPassword.HasTemplate;
        }

        private void NewPasswordCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            originalPasswordName = string.Empty;
            PasswordContract password = new PasswordContract
            {
                TemplateId = selectedCategory != null ? selectedCategory.Id : string.Empty,
                ViewModel = this,
                Name = emptyPasswordTitle
            };
            //            if (selectedCategory!=null) password.Categories.Add(selectedCategory);
            Passwords.Add(password);
            SelectedPassword = password;
            AppDisplayMode = Models.AppDisplayMode.EditPassword;
            if (selectedCategory != null)
            {
                var selected = selectedCategories.FirstOrDefault(c => c.Category == selectedCategory);
                if (selected != null) selected.IsSelected = true;
            }
        }

        private void DeleteCategoryItemCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            FrameworkElement element = e.OriginalSource as FrameworkElement;
            CategoryContract category = element != null ? element.DataContext as CategoryContract : null;
            if (category != null)
            {
                DeleteCategory(category);
            }
        }

        private void ClearSyncPasswordErrorCommand(object sender, ExecutedRoutedEventArgs e)
        {
            SyncDisplayMode = Models.SyncDisplayMode.Login;
        }

        private void SyncLoginCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            PasswordBox box = e.Parameter as PasswordBox;

            if (box == null || DALUtil.IsEqual(Checksum, box.SecurePassword.ComputeHash()))
            {
                if (SyncDisplayMode == Models.SyncDisplayMode.Login)
                {
                    SyncDisplayMode = Models.SyncDisplayMode.None;
                }

                OnSyncLoginCompleted(box != null ? box.SecurePassword : null);
            }
            else
            {
                SyncDisplayMode = Models.SyncDisplayMode.WrongPassword;
            }
            if (box != null) box.Clear();
        }

        private void AddFieldCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;

            FieldContract field = new FieldContract
            {
                ViewModel = this,
                Name = AppResources.CustomFieldName,
                IsCustom = true,
                Id = Guid.NewGuid().ToString()
            };
            EditFields.Add(field);
        }

        private void RemoveFieldCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;

            FieldContract field = (e.OriginalSource as FrameworkElement).DataContext as FieldContract;
            if (field != null)
            {
                if (field.IsCustom)
                {
                    EditFields.Remove(field);
                }
                else
                {
                    field.Value = string.Empty;
                }
            }
        }

        private void CategoriesCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            AppDisplayMode = Models.AppDisplayMode.Categories;
        }


        private void PasswordsCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            AppDisplayMode = Models.AppDisplayMode.Passwords;
        }


        private void ViewPasswordCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            if (SelectedPassword != null)
            {
                AppDisplayMode = Models.AppDisplayMode.Password;
            }
        }


        private void EditPasswordCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            AppDisplayMode = Models.AppDisplayMode.EditPassword;
        }

        private void ToggleFaveCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            var pw = SelectedPassword;
            ToggleFavorite(pw);
        }

        private void DeletePasswordCommand(object sender, ExecutedRoutedEventArgs e)
        {
            e.Handled = true;
            if (MessageBox.Show(AppResources.MsgDeletePassword, AppResources.DeleteBtn, MessageBoxButton.OKCancel) == MessageBoxResult.OK)
            {
                var pw = SelectedPassword;
                DeletePassword(pw);
            }
        }


        #endregion
        #region methods

        private void OnSearchTextChanged()
        {
            if (!string.IsNullOrEmpty(searchText))
            {
                if (DisplayMode != Models.DisplayMode.Search)
                {
                    DisplayMode = Models.DisplayMode.Search;
                }
                else
                {
                    searchResults = new ObservableCollection<PasswordContract>(GetFilteredPasswords());
                    SelectedPasswords = searchResults;
                }
                SelectedPassword = SelectedPasswords.FirstOrDefault();
            }
            else
            {
                if (DisplayMode == Models.DisplayMode.Search) DisplayMode = Models.DisplayMode.Passwords;
            }
        }

        private void OnPasswordGenVisibleChanged()
        {
        }

        public SyncTable GetSyncTable(Guid deviceId)
        {
            return Context.GetSyncTable(deviceId);
        }

        public void SaveSyncTable(SyncTable syncTable)
        {
            Context.SaveSyncTable(syncTable);
        }

        byte[] IDataContext.GetPasswordHash()
        {
            return context.PasswordHash;
        }

        public void ToggleFavorite(PasswordContract password)
        {
            if (password != null)
            {
                bool isFavorite = !password.IsFavorite;
                password.IsFavorite = isFavorite;
                if (isFavorite) Favorites.SortInto(password); else Favorites.Remove(password);
                context.SavePassword(password, bodyOnly: true);
            }
        }

        public void DeletePassword(PasswordContract password)
        {
            if (password != null)
            {
                Guid guid = password.Guid;
                if (guid == Guid.Empty || context.DeletePassword(guid.ToString()))
                {
                    int index = Passwords.IndexOf(password);
                    if (index >= 0)
                    {
                        Passwords.RemoveAt(index);
                        index = Math.Min(index, Passwords.Count - 1);
                        if (index >= 0)
                        {
                            SelectedPassword = Passwords[index];
                        }
                    }
                    SelectedPasswords.Remove(password);
                    Favorites.Remove(password);
                    foreach (CategoryContract c in Categories)
                    {
                        c.Passwords.Remove(password);
                    }
                    password.CategoryIds = null;
                }
            }
        }

        private void LoadPasswords()
        {
            passwords = new ObservableCollection<PasswordContract>();
            //alphaList = new ObservableCollection<AlphaItem>();
            favorites = new ObservableCollection<PasswordContract>();

            var ids = context.ReadPasswordIds();
            var categories = Categories;
            if (ids != null)
            {
                foreach (string id in ids)
                {
                    PasswordContract password = context.ReadPassword(id);
                    if (password == null) continue;
                    password.ViewModel = this;
                    password.Categories.QuietMode = true;
                    passwords.SortInto(password);
                    //alphaList.Add(new AlphaItem(password));
                    if (password.IsFavorite) favorites.SortInto(password);

                    if (password.CategoryIds != null)
                    {
                        foreach (string cid in password.CategoryIds)
                        {
                            CategoryContract category = categories.FindCategory(cid);
                            if (category != null)
                            {
                                category.Passwords.QuietMode = true;
                                category.Passwords.SortInto(password);
                                password.Categories.SortInto(category);
                                category.Passwords.QuietMode = false;
                            }
                        }
                    }
                    password.Categories.QuietMode = false;
                }
            }
        }

        private void OnSelectedPasswordChanged(PasswordContract oldPassword)
        {
            if (oldPassword != null) oldPassword.ChildChanged -= OnPasswordChildChanged;
            OnPropertyChanged("Template");
            PasswordContract pw = this.selectedPassword;
            SelectedCategories = null;
            if (pw != null)
            {
                originalPasswordName = pw.Name;
                pw.ChildChanged += new EventHandler(OnPasswordChildChanged);
            }
        }

        private void OnPasswordChildChanged(object sender, EventArgs e)
        {
            IsTemplateUnlocked = DetermineUnlockedTemplate();
        }

        private bool DetermineUnlockedTemplate()
        {
            if (Template == null) return true;
            if (editFields != null)
            {
                foreach (var field in editFields)
                {
                    if (!string.IsNullOrEmpty(field.Value)) return false;
                }
            }
            return true;
        }

        private void OnEditCategoryChanged(CategoryContract oldCategory)
        {
            if (oldCategory != null)
            {
                oldCategory.PropertyChanged -= OnEditCategoryPropertyChanged;
            }
            CreateIndexedFields();
            CategoryContract category = editCategory;
            IsCategoryModified = false; // category != null ? category.IsModified : false;
            if (category != null)
            {
                originalCategoryName = category.Name;
                category.PropertyChanged += new PropertyChangedEventHandler(OnEditCategoryPropertyChanged);
            }
        }

        private void OnSelectedCategoryChanged(CategoryContract oldCategory)
        {
            CategoryContract category = selectedCategory;

            if (category != null && (appDisplayMode == Models.AppDisplayMode.Passwords || appDisplayMode == Models.AppDisplayMode.EditPassword)) DisplayMode = Models.DisplayMode.Categories;
            if (displayMode == Models.DisplayMode.Categories) SelectedPasswords = category != null ? category.Passwords : null;
            IsNewPasswordEnabled = selectedCategory != null;
        }

        private void OnEditCategoryPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            IsCategoryModified = true;
        }

        private void OnAppDisplayModeChanged(AppDisplayMode oldMode)
        {
            switch (oldMode)
            {
                case Models.AppDisplayMode.EditPassword:
                    EditFields = null;
                    break;

                case Models.AppDisplayMode.Categories:
                    CategoryContract category = editCategory;
                    if (category != null) category.UndoCategory(this);
                    EditCategory = null;
                    break;

            }
            switch (appDisplayMode)
            {
                case Models.AppDisplayMode.Passwords:
                    //SelectedPasswords = Passwords;
                    if (SelectedPassword == null) OnDisplayModeChanged(displayMode);
                    else
                    {
                        ListUtil.Fill(selectedPasswords, _selectedPasswords);
                    }
                    EditCategory = null;
                    break;

                case Models.AppDisplayMode.Categories:
                    if (editCategory == null) EditCategory = categories.FirstOrDefault();
                    CreateIndexedFields();
                    break;

                case Models.AppDisplayMode.EditPassword:
                    SelectedCategories = selectedPassword != null ? Models.SelectedCategory.GetCollection(this, selectedPassword) : null;
                    EditFields = selectedPassword != null ? selectedPassword.GetEditFields(this) : null;
                    IsTemplateUnlocked = DetermineUnlockedTemplate();
                    EditCategory = null;
                    break;

                default:
                    EditCategory = null;
                    SelectedCategories = null;
                    EditFields = null;
                    break;
            }

            MinWidth = GetMinWidthFromAppDisplayMode(appDisplayMode);

            var eh = AppDisplayModeChanged;
            if (eh != null) eh(this, EventArgs.Empty);
        }

        private static double GetMinWidthFromAppDisplayMode(Models.AppDisplayMode appDisplayMode)
        {
            switch (appDisplayMode)
            {
                case AppDisplayMode.Categories:
                    return 650;

                case AppDisplayMode.Password:
                case AppDisplayMode.Passwords:
                case AppDisplayMode.Login:
                    return 320d;

                default: return 450d;
            }
        }

        private void CreateIndexedFields()
        {
            CategoryContract category = this.editCategory;
            CategoryEditFields = category != null ? category.Fields : null;
        }

        private void OnDisplayModeChanged(DisplayMode oldMode)
        {
            if (displayMode != Models.DisplayMode.Search)
            {
                SearchText = string.Empty;
                searchResults = null;
            }

            if (appDisplayMode == Models.AppDisplayMode.Passwords)
            {
                switch (displayMode)
                {
                    case Models.DisplayMode.Search:
                        searchResults = new ObservableCollection<PasswordContract>(GetFilteredPasswords());
                        SelectedPasswords = searchResults;
                        break;

                    case DisplayMode.Passwords:
                        if (oldMode == Models.DisplayMode.Categories)
                        {
                            hiddenCategoryIndex = Categories.IndexOf(selectedCategory);
                        }
                        SelectedPasswords = Passwords;
                        SelectedCategory = null;
                        break;


                    case Models.DisplayMode.Favorites:
                        if (oldMode == Models.DisplayMode.Categories)
                        {
                            hiddenCategoryIndex = Categories.IndexOf(selectedCategory);
                        }
                        SelectedPasswords = Favorites;
                        SelectedCategory = null;
                        break;

                    case Models.DisplayMode.Categories:
                        if (selectedCategory == null)
                        {
                            int index = hiddenCategoryIndex;
                            if (index >= Categories.Count) index = Categories.Count - 1;
                            if (index < 0) index = 0;
                            SelectedCategory = Categories.Count > index ? Categories[index] : null;
                        }
                        SelectedPasswords = selectedCategory != null ? selectedCategory.Passwords : null;
                        break;

                }
            }
        }

        /// <summary>
        /// Gets all the passwords which match the search text.
        /// </summary>
        private IEnumerable<PasswordContract> GetFilteredPasswords()
        {
            return Passwords.Where(p => p.Name.IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0);
        }

        private void OnTemplateChanged(CategoryContract oldTemplate)
        {
            if (selectedPassword != null)
            {
                if (oldTemplate != null)
                {
                    var selected = selectedCategories.FirstOrDefault(c => c.Category == oldTemplate);
                    if (selected != null) selected.IsSelected = false;
                }
                CategoryContract template = Template;
                EditFields = selectedPassword.GetEditFields(this);
                if (template != null)
                {
                    var selected = selectedCategories.FirstOrDefault(c => c.Category == template);
                    if (selected != null) selected.IsSelected = true;
                }
            }
        }

        private bool AutoChangeCurrentCategory()
        {
            return displayMode == Models.DisplayMode.Categories
                ||
                appDisplayMode != Models.AppDisplayMode.Passwords;
        }

        public void DeleteCategory(CategoryContract category)
        {
            if (category == null) throw new ArgumentNullException("category");
            DataUtil.DeleteCategory(category, this);
        }

        public CategoryContract AddCategory()
        {
            IsCategoryFocused = false;
            CategoryContract category = new CategoryContract();
            //            Categories.Add(category);
            category.Name = string.Empty;
            EditCategory = category;
            return category;
        }

        public CategoryContract FindCategory(string id)
        {
            return Categories.FirstOrDefault(c => c.Id == id);
        }

        public PasswordContract FindPassword(Guid guid)
        {
            return Passwords.FirstOrDefault(p => p.Guid == guid);
        }

        public void AddPassword(PasswordContract password)
        {
            password.ViewModel = this;
            Passwords.SortInto(password);
            SavePassword(password, password.Name);
        }

        public void AddCategory(CategoryContract category)
        {
            DataUtil.SaveCategory(category, this, category.Name);
        }

        /// <summary>
        /// Gets the password of another device to sync with, either async by a dialog, or cached from previously stored passwords.
        /// </summary>
        /// <param name="deviceId">Guid of the device for which to get the password (note that the Id does not change as long as the device app is installed.</param>
        /// <param name="checksum">Hash key of the password to get.</param>
        /// <returns>SecureString containing the password, otherwise null to cancel.</returns>
        public SecureString GetSyncPassword(Guid deviceId, byte[] checksum)
        {
            //if (SyncDisplayMode != Models.SyncDisplayMode.None) return null;

            if (Context.Password != null && DALUtil.IsEqual(checksum, Context.Password.ComputeHash())) return Context.Password;

            using (ManualResetEvent manualResetEvent = new ManualResetEvent(false))
            {
                this.Checksum = checksum;
                EventHandler<SyncPasswordEventArgs> handler = null;
                SecureString password = null;
                handler = (sender, e) =>
                {
                    SyncLoginCompleted -= handler;
                    password = e.Password;
                    manualResetEvent.Set();
                };

                SyncLoginCompleted += handler;
                App.Current.Dispatcher.BeginInvoke((Action)delegate
                {
                    SyncDisplayMode = Models.SyncDisplayMode.Login;
                });
                //while (password == null) Thread.Sleep(50);
                bool success = manualResetEvent.WaitOne(TimeSpan.FromMinutes(1.0)) && password != null;
                SyncLoginCompleted -= handler;
                App.Current.Dispatcher.BeginInvoke((Action)delegate
                {
                    if (SyncDisplayMode == Models.SyncDisplayMode.Login)
                    {
                        SyncDisplayMode = Models.SyncDisplayMode.None;
                    }
                });
                this.Checksum = null;

                return password;
            }
        }


        /// <summary>
        ///  Show the sync tray no longer than 10 seconds, and expand the duration each time the sync context updates progress:
        /// </summary>
        private void RestartSyncElapsedTimer()
        {
            App.Current.Dispatcher.Invoke((Action)delegate
            {
                if (this.syncTimer == null)
                {
                    DispatcherTimer timer = new DispatcherTimer();
                    timer.Tick += delegate
                    {
                        timer.Stop();
                        timer.IsEnabled = false;
                        SyncDisplayMode = Models.SyncDisplayMode.None;
                    };
                    timer.Interval = TimeSpan.FromSeconds(10);

                    this.syncTimer = timer;
                }
                syncTimer.Start();
                syncTimer.IsEnabled = true;
            });
        }


        public void UpdateProgress()
        {
            SyncDisplayMode = Models.SyncDisplayMode.Synchronize;
            RestartSyncElapsedTimer();
        }

        protected void OnSyncLoginCompleted(SecureString password)
        {
            var eh = SyncLoginCompleted;
            if (eh != null) eh(this, new SyncPasswordEventArgs(password));
        }


        #endregion
        #region events

        public event EventHandler AppDisplayModeChanged;
        public event EventHandler<SyncPasswordEventArgs> SyncLoginCompleted;

        #endregion

    }
}
